package com.googlecode.cswish.util;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

@Service
public class XMLUtil {

	private final static Log logger = LogFactory.getLog(XMLUtil.class);
	
	private TransformerFactory transformerFactory;
	private DocumentBuilderFactory documentFactory;
	private XPathFactory xpathFactory;

	public XMLUtil() {
		transformerFactory = TransformerFactory.newInstance();
		documentFactory = DocumentBuilderFactory.newInstance();
		xpathFactory = XPathFactory.newInstance();
	}
	
	public void removeChildren(Node node) {
		NodeList childNodes = node.getChildNodes();
		int length = childNodes.getLength();
		for (int i = length - 1; i > -1; i--) {
			node.removeChild(childNodes.item(i));
		}
	}
	
	public void replace(Document document, String path, String ctx) {
		Node node = document.createTextNode(ctx);
		replace(document, path, node);
	}
	
	public void replace(Document document, String path, Node node) {
		try {
			Node oldNode = findPath(document, path);
			if (oldNode == null) {
				String idPath = "//*[@id='" + path + "']";
				oldNode = findPath(document, idPath);
			}
			if (oldNode != null) {
				oldNode.getParentNode().replaceChild(node, oldNode);
			} else {

				logger.info("Failed to find node: " + path);
			}
		} catch (Exception ex) {
			logger.error("Failed to replace document", ex);
		}
	}
	
	private Node findPath(Document document, String path) {
		XPath xpath = xpathFactory.newXPath();
		try {
			XPathExpression expr = xpath.compile(path);
			NodeList nodeList = (NodeList) expr.evaluate(document, XPathConstants.NODESET);
			if (nodeList.getLength() > 0) {
				return nodeList.item(0);
			}
		} catch(Exception ex) {
			logger.error("Failed to find " + path, ex);
		}
		return null;
	}

	public Document loadDocument(String file) {
		try {
			DocumentBuilder builder = documentFactory.newDocumentBuilder();
			return builder.parse(file);
		} catch(Exception ex) {
			logger.error("Failed to load document", ex);
			return null;
		}
	}
	
	public String toString(Document document) {
		try {
			StringWriter output = new StringWriter();
			Transformer transformer = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(document);
			StreamResult result = new StreamResult(output);
			transformer.transform(source, result);
			return output.toString();
		} catch(Exception ex) {
			logger.error("Failed to save document", ex);
			return null;
		}
	}

	public void saveDocument(Document dom, String file) throws TransformerException, IOException {	
		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, dom.getDoctype().getPublicId());
		transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dom.getDoctype().getSystemId());

		DOMSource source = new DOMSource(dom);
		StreamResult result = new StreamResult();

		FileOutputStream outputStream = new FileOutputStream(file);
		result.setOutputStream(outputStream);
		transformer.transform(source, result);

		outputStream.flush();
		outputStream.close();
	}
}